
function initializeGlobals()
	player = { commodities = {} }

	warp = nil
	popUps = nil
	page = nil
	absoluteDay = nil
end

function newGame()
	initializeGlobals()

	player = {
		credits = 1500,
		commodities = {},
		ship = newShip("Llama"),
		planet = planetList[3]
	}

	for i, commodity in ipairs(commodityList) do
		player.commodities[commodity.name] = 0
	end

	changeCursor("Commodities")

	for i, planet in ipairs(planetList) do
		planet.coordinates.x = planet.startingCoordinates.x
		planet.coordinates.y = planet.startingCoordinates.y
	end

	absoluteDay = 1
	local days = 366
	passDays(366)

	return true
end

function performAttack(ship1, ship2)
	local hitChance = 0.75
	if (math.random() < hitChance) then
		ship2.hullHealth = ship2.hullHealth - ship1.attack
	end
end

function getPlanetNews(planetId)
	local planet = planetList[planetId]
	local result = {}
	for i, commodity in ipairs(commodityList) do
		if (planet.commodityRates[commodity.name] < 0.67) then
			table.insert(result, commodity.cheapText)
		elseif (planet.commodityRates[commodity.name] > 1.5) then
			table.insert(result, commodity.expensiveText)
		end
	end
	if (#result == 0) then
		table.insert(result, "I haven't heard news from here.")
	end
	while (#result > 3) do
		table.remove(result, math.ceil(math.random() * #result))
	end
	return result
end

function revolvePlanet(planet, days)
	local distance = calculatePlanetDistance(planet, sun)
	local x1 = planet.coordinates.x
	local y1 = planet.coordinates.y
	local centerX = sun.coordinates.x
	local centerY = sun.coordinates.y
	local deltaTheta = 6.283185307179586 * (days / planet.yearTime)
	local theta1 = math.atan((y1 - centerY) / (x1 - centerX))
	local theta2 = theta1 + deltaTheta
	local deltaY2 = distance * math.sin(theta2)
	local deltaX2 = distance * math.cos(theta2)
	planet.coordinates.x = centerX + deltaX2
	planet.coordinates.y = centerY + deltaY2
end

function updateCommodityRates(planet, days)
	local updateProbability = 1 - (0.8409 ^ days)
	for i, commodity in ipairs(commodityList) do
		if (math.random() < updateProbability) then
			local newRate = 3 ^ (math.random() * 2 - 1)
			local newAmount = math.floor((3 ^ (math.random() * 2 - 1)) * commodity.baseStock)
			planet.commodityRates[commodity.name] = newRate
			planet.commodities[commodity.name] = newAmount
		end
	end
end

function passDays(numDays)
	for i, planet in ipairs(planetList) do
		revolvePlanet(planet, numDays)
		updateCommodityRates(planet, numDays)
	end
	absoluteDay = absoluteDay + numDays
end

function newShip(shipId)
	local result = {}
	local proto = shipList[shipId]
	result.model = shipId
	result.cost = proto.cost
	result.hullHealth = proto.hullStrength
	result.hullStrength = proto.hullStrength
	result.attack = proto.attack
	result.speed = proto.speed
	result.fuel = proto.maxFuel
	result.maxFuel = proto.maxFuel
	return result
end

function charToNum(c)
	for i,c2 in ipairs(alphaNumMap) do
		if (c == c2) then
			return i - 1
		end
	end
end

function numToChar(n)
	return alphaNumMap[n + 1]
end

function checkHighScore()
	sortHighScores()
	if (#highScores < 5 or player.credits > highScores[#highScores].score) then
		newHighScore = {
			name = { " ", " ", " " },
			score = player.credits,
			x = 1,
			y = 1
		}
		return true
	end
	return false
end

function printHighScores()
	local result = {}
	for i=1, 5 do
		if (highScores[i] ~= nil) then
			local name = highScores[i].name[1] .. highScores[i].name[2] .. highScores[i].name[3]
			local score = highScores[i].score
			table.insert(result, name .. " - " .. score)
		end
	end
	addPopUp(result)
end

function sortHighScores()
	table.sort(highScores, function(k1, k2) return k1.score > k2.score end)
end

function performSystemAction()
	local action = getSelection(page, 2)
	if (action == "Retire Pilot") then
		changeCursor("Game Over")
		return true
	elseif (action == "Exit Game") then
		exitGame = true
		return true
	elseif (action == "High Scores") then
		printHighScores()
		return true
	end
end

function tradeInShip()
	local shipId = getSelection(page, 3)
	local cost = shipList[shipId].cost
	local tradeInCredit = shipResaleFactor * player.ship.cost
	if (player.credits >= cost - tradeInCredit) then
		player.ship = newShip(shipId)
		player.credits = player.credits - cost + tradeInCredit
		return true
	else
		return false, { "You do not have enough credits to trade in." }
	end
end

function purchaseServices()
	local action = getSelection(page, 2)
	local cost = serviceList[action].cost
	if (player.credits >= cost) then
		if (action == "Repair Ship") then
			if (player.ship.hullHealth < player.ship.hullStrength) then
				player.ship.hullHealth = player.ship.hullStrength
				player.credits = player.credits - cost
				return true
			else
				return false, { "Your ship is already fully repaired." }
			end
		elseif (action == "Refuel Ship") then
			if (player.ship.fuel < player.ship.maxFuel) then
				player.ship.fuel = player.ship.maxFuel
				player.credits = player.credits - cost
				return true
			else
				return false, { "Your ship is already fully fueled." }
			end
		elseif (action == "Sleep 1 Night") then
			passDays(1)
			player.credits = player.credits - cost
			return true
		elseif (action == "Sleep 10 Nights") then
			passDays(10)
			player.credits = player.credits - cost
			return true
		end
	else
		return false, { "You do not have the required funds." }
	end
end

function engageEnemy()
	local action = getSelection(page, 2)
	if (action == "Flee") then
		local flightChance = 1 - math.min(0.8, player.ship.speed / warp.enemyShip.speed)
		if (math.random() < flightChance) then
			clearWarpEncounter()
			return true, { "You escaped successfully." }
		else
			performAttack(warp.enemyShip, player.ship)
			if (player.ship.hullHealth <= 0) then
				changeCursor("Game Over")
				return true, { "You were killed while fleeing." }
			end
		end
	elseif (action == "Surrender") then
		player.credits = math.ceil(player.credits * 0.1)
		for i, commodity in ipairs(commodityList) do
			player.commodities[commodity.name] = 0
		end
		clearWarpEncounter()
		return true, { "You surrendered." }
	else
		performAttack(player.ship, warp.enemyShip)
		if (warp.enemyShip.hullHealth <= 0) then
			clearWarpEncounter()
			return true, { "You defeated the enemy." }
		else
			performAttack(warp.enemyShip, player.ship)
			if (player.ship.hullHealth <= 0) then
				changeCursor("Game Over")
				return true, { "You were killed in the fight." }
			end
		end
	end
	return true
end

function hasContraband(player)
	for i, stock in ipairs(player.commodities) do
		if (stock > 0 and not commodityList[i].legal) then
			return true
		end
	end
	return false
end

function enterPirateCheck()
	local enemyShipType = "Llama"
	warp.enemyShip = newShip(enemyShipType)
	warp.hasEncounter = true
	addPopUp({ "You encounter a pirate ship,", "demanding credits." })
	changeCursor("Pirate Encounter")
end

function enterCopCheck()
	local enemyShipType = "Llama"
	warp.enemyShip = newShip(enemyShipType)
	warp.hasEncounter = true
	addPopUp({ "You encounter a cop ship,", "demanding credits." })
	changeCursor("Cop Encounter")
end

function clearWarpEncounter()
	warp.enemyShip = nil
	warp.guaranteeSafe = true
	page = nil
end

function continueWarp()
	local guaranteeSafe = warp.guaranteeSafe
	local source = warp.source
	local target = warp.target
	local copCheckChance
	local pirateCheckChance
	if (guaranteeSafe) then
		warp.guaranteeSafe = false
		copCheckChance = 0
		pirateCheckChance = 0
	else
		copCheckChance = math.max(source.lawfulRating, target.lawfulRating) * 0.25
		pirateCheckChance = (1 - math.min(source.lawfulRating, target.lawfulRating)) * 0.25
	end
	if (warp.iter <= warp.maxIters) then
		if (math.random() < copCheckChance and hasContraband(player)) then
			enterCopCheck()
		elseif (math.random() < pirateCheckChance) then
			enterPirateCheck()
		else
			page = nil
			player.ship.fuel = player.ship.fuel - 1
			warp.iter = warp.iter + 1
		end
	else
		changeCursor("Commodities")
		player.planet = target
		if (warp.hasEncounter) then
			addPopUp({ "The rest of your trip is uneventful." })
		else
			addPopUp({ "Your trip is uneventful." })
		end
		warp = nil
	end
end

function getDistortedRelativeCoordinates(planet1, planet2)
	local deltaX = planet1.coordinates.x - planet2.coordinates.x
	local deltaY = planet1.coordinates.y - planet2.coordinates.y
	local scaleFactorX, scaleFactorY
	if (deltaX == 0) then
		scaleFactorX = 0
	else
		scaleFactorX = math.max(0, 12 * math.log(math.abs(deltaX) / 16)) / math.abs(deltaX)
	end
	if (deltaY == 0) then
		scaleFactorY = 0
	else
		scaleFactorY = math.max(0, 3 * math.log(math.abs(deltaY) / 16)) / math.abs(deltaY)
	end
	return deltaX * scaleFactorX, deltaY * scaleFactorY
end

function calculatePlanetDistance(planet1, planet2)
	local x1, y1, x2, y2
	x1 = planet1.coordinates.x
	y1 = planet1.coordinates.y
	x2 = planet2.coordinates.x
	y2 = planet2.coordinates.y
	return math.sqrt((y2-y1)*(y2-y1)+(x2-x1)*(x2-x1))
end

function performPlanetAction()
	local action = getSelection(page, 2)
	local target = getSelection(page, 3)
	local result, msg
	if (action == "Warp Here") then
		result, msg = initiateWarp()
	else
		msg = getPlanetNews(target)
		result = true
	end
	return result, msg
end

function initiateWarp()
	local planet = player.planet
	local id = getSelection(page, 3)
	local targetPlanet = planetList[id]
	local distance = calculatePlanetDistance(planet, targetPlanet)
	local maxIters = math.ceil(distance / 10)
	if (player.ship.fuel >= maxIters) then
		passDays(1)
		page = nil
		popUps = nil
		warp = {
			source=planet,
			target=targetPlanet,
			maxIters=maxIters,
			iter=1,
			hasEncounter=false,
			guaranteeSafe = true
		}
		warp.guaranteeSafe = true
		return true
	else
		return false, { "You do not have the required fuel." }
	end
end

function getNeighbors(planetId)
	local result = {}
	for i, con in ipairs(planetConnections) do
		if (con[1] == planetId) then
			table.insert(result, con[2])
		elseif (con[2] == planetId) then
			table.insert(result, con[1])
		end
	end
	return result
end

function changeCursor(pageId)
	local planetOptions = { "Commodities", "Planets", "Services", "Shipyard", "System" }

	if (pageId == "Commodities") then
		page = {
			id = "Commodities",
			level = 1,
			options = {
				planetOptions,
				{ "Buy 1", "Buy 5", "Buy 10", "Buy Max", "Sell 1", "Sell 5", "Sell 10", "Sell Max" },
				player.planet.commodityList
			},
			selections = {
				1,
				1,
				1
			}
		}
	elseif (pageId == "Planets") then
		page = {
			id = "Planets",
			level = 1,
			options = {
				planetOptions,
				{ "Warp Here", "See News" },
				getNeighbors(player.planet.name)
			},
			selections = {
				2,
				1,
				1
			}
		}
	elseif (pageId == "Services") then
		page = {
			id = "Services",
			level = 1,
			options = {
				planetOptions,
				{ "Repair Ship", "Refuel Ship", "Sleep 1 Night", "Sleep 10 Nights" }
			},
			selections = {
				3,
				1
			}
		}
	elseif (pageId == "Shipyard") then
		page = {
			id = "Shipyard",
			level = 1,
			options = {
				planetOptions,
				{ "Buy Ship" },
				player.planet.shipyard
			},
			selections = {
				4,
				1,
				1
			}
		}
	elseif (pageId == "System") then
		page = {
			id = "System",
			level = 1,
			options = {
				planetOptions,
				{ "Exit Game", "Retire Pilot", "High Scores" }
			},
			selections = {
				5,
				1
			}
		}
	elseif (pageId == "Pirate Encounter") then
		page = {
			id = "Pirate Encounter",
			level = 2,
			options = {
				{ "Pirate Encounter" },
				{ "Flee", "Surrender", "Attack" }
			},
			selections = {
				1,
				1
			}
		}
	elseif (pageId == "Cop Encounter") then
		page = {
			id = "Cop Encounter",
			level = 2,
			options = {
				{ "Cop Encounter" },
				{ "Flee", "Surrender", "Attack" }
			},
			selections = {
				1,
				1
			}
		}
	elseif (pageId == "Game Over") then
		page = {
			id = "Game Over",
			level = 2,
			options = {
				{ "Game Over" },
				{ "Try Again" }
			},
			selections = {
				1,
				1
			}
		}
		if (not checkHighScore()) then
			printHighScores()
		end
	end
end

function getSelection(page, level)
	local options = page.options[level]
	return options[page.selections[level]]
end

function transactCommodity()
	local planet = player.planet
	local id = getSelection(page, 3)
	local action = getSelection(page, 2)
	local amount, buyer, seller, unitCost
	if (action == "Buy 1" or action == "Buy 5" or action == "Buy 10") then
		if (action == "Buy 1") then
			amount = 1
		elseif (action == "Buy 5") then
			amount = 5
		else
			amount = 10
		end
		buyer = player
		seller = planet
		unitCost = math.floor(planet.commodityRates[id] * commodityList[id].baseCost)
	elseif (action == "Buy Max") then
		buyer = player
		seller = planet
		unitCost = math.floor(planet.commodityRates[id] * commodityList[id].baseCost)
		if (math.floor(buyer.credits / unitCost) < seller.commodities[id]) then
			amount = math.floor(buyer.credits / unitCost)
		else
			amount = seller.commodities[id]
		end
	elseif (action == "Sell 1" or action == "Sell 5" or action == "Sell 10") then
		if (action == "Sell 1") then
			amount = 1
		elseif (action == "Sell 5") then
			amount = 5
		else
			amount = 10
		end
		buyer = planet
		seller = player
		unitCost = math.floor(planet.commodityRates[id] * commodityList[id].baseCost * commodityResaleFactor)
	else
		buyer = planet
		seller = player
		unitCost = math.floor(planet.commodityRates[id] * commodityList[id].baseCost * commodityResaleFactor)
		if (math.floor(buyer.credits / unitCost) < seller.commodities[id]) then
			amount = math.floor(buyer.credits / unitCost)
		else
			amount = seller.commodities[id]
		end
	end
	if (buyer.credits < unitCost * amount) then
		return false, { "You do not have the required credits." }
	elseif (seller.commodities[id] < amount) then
		return false, { "There is nothing to buy or sell." }
	else
		buyer.commodities[id] = buyer.commodities[id] + amount
		seller.commodities[id] = seller.commodities[id] - amount
		buyer.credits = buyer.credits - unitCost * amount
		seller.credits = seller.credits + unitCost * amount
		return true
	end
end

function attemptAction()
	local actionSuccessful, msg
	if (page.id == "Commodities") then
		actionSuccessful, msg = transactCommodity()
	elseif (page.id == "Planets") then
		actionSuccessful, msg = performPlanetAction()
	elseif (page.id == "Services") then
		actionSuccessful, msg = purchaseServices()
	elseif (page.id == "Shipyard") then
		actionSuccessful, msg = tradeInShip()
	elseif (page.id == "System") then
		actionSuccessful, msg = performSystemAction()
	elseif (page.id == "Pirate Encounter" or page.id == "Cop Encounter") then
		actionSuccessful, msg = engageEnemy()
	elseif (page.id == "Game Over") then
		actionSuccessful, msg = newGame()
	end
	return actionSuccessful, msg
end

function addPopUp(msgList)
	if (popUps == nil) then
		popUps = {}
	end
	table.insert(popUps, msgList)
end

function removePopUp()
	table.remove(popUps)
	if (#popUps == 0) then
		popUps = nil
	end
end

function onButtonPress(inputCommand)
	if (inputCommand == "exit") then
		exitGame = true
	elseif (newHighScore ~= nil) then
		if (inputCommand == "up") then
			if (newHighScore.y == 1) then
				newHighScore.name[newHighScore.x] = numToChar((charToNum(newHighScore.name[newHighScore.x])) % #alphaNumMap + 1)
			else
				newHighScore.y = 2
			end
		elseif (inputCommand == "down") then
			if (newHighScore.y == 1) then
				newHighScore.name[newHighScore.x] = numToChar((charToNum(newHighScore.name[newHighScore.x]) - 2 + #alphaNumMap) % #alphaNumMap + 1)
			end
		elseif (inputCommand == "left") then
			if (newHighScore.y == 1) then
				newHighScore.x = (newHighScore.x + 1) % 3 + 1
			end
		elseif (inputCommand == "right") then
			if (newHighScore.y == 1) then
				newHighScore.x = newHighScore.x % 3 + 1
			end
		elseif (inputCommand == "a") then
			if (newHighScore.y == 1) then
				newHighScore.y = 2
			else
				if (#highScores == 5) then
					highScores[5] = newHighScore
				else
					table.insert(highScores, newHighScore)
				end
				newHighScore = nil
				sortHighScores()
				saveHighScores()
				printHighScores()
			end
		elseif (inputCommand == "b") then
			if (newHighScore.y == 2) then
				newHighScore.y = 1
			end
		end
	elseif (popUps ~= nil and #popUps > 0) then
		if (inputCommand == "a") then
			table.remove(popUps)
		end
	elseif (inputCommand == "up") then
		if (page.level < #page.options) then
			page.level = page.level + 1
		end
	elseif (inputCommand == "down") then
		local minLevel = 1
		if (#page.options[1] <= 1) then
			minLevel = 2
		end
		if (page.level > minLevel) then
			page.level = page.level - 1
		end
	elseif (inputCommand == "left") then
		if (page.selections[page.level] > 1) then
			page.selections[page.level] = page.selections[page.level] - 1
		else
			page.selections[page.level] = #page.options[page.level]
		end
		if (page.level == 1) then
			changeCursor(page.options[1][page.selections[1]])
		end
	elseif (inputCommand == "right") then
		if (page.selections[page.level] < #page.options[page.level]) then
			page.selections[page.level] = page.selections[page.level] + 1
		else
			page.selections[page.level] = 1
		end
		if (page.level == 1) then
			changeCursor(page.options[1][page.selections[1]])
		end
	elseif (inputCommand == "a" and page ~= nil) then
		if (page.level == 2) then
			local actionSuccessful, msg = attemptAction()
			if (msg ~= nil) then
				addPopUp(msg)
			end
		else
			page.level = 2
		end
	elseif (inputCommand == "b") then
		exitGame = false
	elseif (inputCommand == "x") then
		exitGame = false
	elseif (inputCommand == "y") then
		exitGame = false
	end
end

exitGame = false
gameLoaded = false
highScores = {}
alphaNumMap = { " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "!", "?", "." }
